<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Frame;

use Exception;
use ReflectionClass;
use ReflectionException;
use Google\Protobuf\Internal\GPBUtil;
use Srv\Libs\Common\CommJson;
use Srv\Libs\Common\CommString;

abstract class GPBUtilAbstract extends GPBUtil
{
    /**
     * @param string $name
     * @param array $arguments
     * @return void
     * @throws Exception
     * __callStatic
     */
    public static function __callStatic(string $name, array $arguments)
    {
        $nameLength     = strlen($name);
        if($nameLength > 3 && substr($name, 0, 3) === 'GPB' && count($arguments) > 0 && isset($arguments[0]) && is_string($arguments[0])){
            call_user_func_array([GPBUtil::class, substr($name, 3)], array_slice($arguments, 1));
        }else{
            throw new Exception('Calling static method not found');
        }
    }

    /**
     * @param string $method
     * @param $var
     * @param $check_utf8
     * @return void
     * @throws Exception
     * GPBcheckString
     */
    public static function GPBcheckString(string $method, &$var, $check_utf8)
    {
        parent::checkString($var, $check_utf8);
        self::checkDbColumnValue($method, $var);
    }

    /**
     * @param string $method
     * @param $var
     * @param string $classNameEnum
     * @return void
     * @throws Exception
     * GPBcheckEnum
     */
    public static function GPBcheckEnum(string $method, &$var, string $classNameEnum = '')
    {
        parent::checkEnum($var);
        if(strlen($classNameEnum) > 0 && is_subclass_of($classNameEnum, EnumAbstract::class, true) && !$classNameEnum::valueExists($var)) throw new Exception($method.', Enum value error, '.$var);
    }

    /**
     * @param string $method
     * @param $var
     * @return void
     * @throws Exception
     * GPBcheckInt32
     */
    public static function GPBcheckInt32(string $method, &$var)
    {
        parent::checkInt32($var);
        self::checkDbColumnValue($method, $var);
    }

    /**
     * @param string $method
     * @param $var
     * @return void
     * @throws Exception
     * GPBcheckUint32
     */
    public static function GPBcheckUint32(string $method, &$var)
    {
        parent::checkUint32($var);
        self::checkDbColumnValue($method, $var);
    }

    /**
     * @param string $method
     * @param $var
     * @return void
     * @throws Exception
     * GPBcheckInt64
     */
    public static function GPBcheckInt64(string $method, &$var)
    {
        parent::checkInt64($var);
        self::checkDbColumnValue($method, $var);
    }

    /**
     * @param string $method
     * @param $var
     * @return void
     * @throws Exception
     * GPBcheckUint64
     */
    public static function GPBcheckUint64(string $method, &$var)
    {
        parent::checkUint64($var);
        self::checkDbColumnValue($method, $var);
   }

    /**
     * @param string $method
     * @param $var
     * @return void
     * @throws Exception
     * GPBcheckFloat
     */
    public static function GPBcheckFloat(string $method, &$var)
    {
        parent::checkFloat($var);
        self::checkDbColumnValue($method, $var);
    }

    /**
     * @param string $method
     * @param $var
     * @return void
     * @throws Exception
     * GPBcheckDouble
     */
    public static function GPBcheckDouble(string $method, &$var)
    {
        parent::checkDouble($var);
        self::checkDbColumnValue($method, $var);
    }

    /**
     * @param string $method
     * @param $var
     * @return void
     * @throws Exception
     * GPBcheckBool
     */
    public static function GPBcheckBool(string $method, &$var)
    {
        unset($method);
        parent::checkBool($var);
    }

    /**
     * @param string $method
     * @param $var
     * @param $klass
     * @return void
     * @throws Exception
     * GPBcheckMessage
     */
    public static function GPBcheckMessage(string $method, &$var, $klass)
    {
        unset($method);
        parent::checkMessage($var, $klass);
    }

    /**
     * @param string $method
     * @param $var
     * @param $type
     * @param $klass
     * @return void
     * @throws Exception
     * GPBcheckRepeatedField
     */
    public static function GPBcheckRepeatedField(string $method, &$var, $type, $klass = null)
    {
        unset($method);
        parent::checkRepeatedField($var, $type, $klass);
    }

    /**
     * @param string $method
     * @param $var
     * @param $key_type
     * @param $value_type
     * @param $klass
     * @return void
     * @throws Exception
     * GPBcheckMapField
     */
    public static function GPBcheckMapField(string $method, &$var, $key_type, $value_type, $klass = null)
    {
        unset($method);
        parent::checkMapField($var, $key_type, $value_type, $klass);
    }

    /**
     * @param string $method
     * @param $val
     * @return float|int|object|string
     * @throws Exception
     * GPBgetVal
     */
    public static function GPBgetVal(string $method, $val)
    {
        if(strstr($method, 'DBO::') === false) return $val;
        $type           = '';
        $columnName     = '';
        $columnTypeStr  = '';
        $columnType     = '';
        $unsigned       = 0;
        if(!self::parseMethod($method, $type, $columnName, $columnTypeStr, $columnType, $unsigned) || strlen($columnType) < 1 || strlen($columnName) < 1 || strlen($columnTypeStr) < 1) return $val;
        $method         = str_replace('::', '->', $method);
        switch (strtolower($columnType)){
            case 'bit':
            case 'tinyint':
            case 'smallint':
            case 'mediumint':{
                return is_int($val) ? $val : intval($val);
            }
            case 'int':
            case 'timestamp':{
                if(!is_int($val)) $val = intval($val);
                if($unsigned === 1 && $val < 0) $val = pow(2, 32) + $val;
                return $val;
            }
            case 'serial':
            case 'bigint': {
                if($unsigned === 1 && $val < 0) $val = bcadd(bcpow('2', '64'), strval($val));
                $getExtData     = func_get_arg(2);
                if(!is_array($getExtData) || !isset($getExtData[0])){
                    if(is_int($val)) return $val;
                    if(is_string($val) && !(bccomp($val, '-9223372036854775808') === -1 || bccomp($val, '9223372036854775807') === 1)) return intval($val);
                    return $val;
                }else{
                    if($getExtData[0] === true){
                        if(is_string($val)) return $val;
                        if(is_int($val)) return strval($val);
                    }else{
                        if(is_int($val)) return $val;
                        if(is_string($val)){
                            if(bccomp($val, '-9223372036854775808') === -1 || bccomp($val, '9223372036854775807') === 1) throw new Exception($method.', Number val range [-9223372036854775808, 9223372036854775807], '.$val);
                            return intval($val);
                        }
                    }
                    throw new Exception($method.', Number val must type is int or string: '.gettype($val).', '.$val);
                }
            }
            case 'decimal':
            case 'float':
            case 'double':
            case 'real':{
                return is_float($val) ? $val : floatval($val);
            }
            default:{
                return $val;
            }
        }
    }

    /**
     * @param string $method
     * @param $var
     * @return void
     * @throws Exception
     * checkDbColumnValue
     */
    private static function checkDbColumnValue(string $method, $var)
    {
        if(strstr($method, 'DBO::') === false) return;
        $type           = '';
        $columnName     = '';
        $columnTypeStr  = '';
        $columnType     = '';
        $unsigned       = 0;
        if(!self::parseMethod($method, $type, $columnName, $columnTypeStr, $columnType, $unsigned) || strlen($columnType) < 1 || strlen($columnName) < 1 || strlen($columnTypeStr) < 1) return;
        $method         = str_replace('::', '->', $method);
        switch (strtolower($columnType)){
            case 'bit':{
                if($var < 1 || $var > 64) throw new Exception($method.', Number range [1, 64], '.$var);
                break;
            }
            case 'tinyint':{
                if($unsigned === 1){
                    if($var < 0 || $var > 255) throw new Exception($method.', Number range [0, 255], '.$var);
                }else{
                    if($var < -128 || $var > 127) throw new Exception($method.', Number range [-128, 127], '.$var);
                }
                break;
            }
            case 'smallint':{
                if($unsigned === 1){
                    if($var < 0 || $var > 65535) throw new Exception($method.', Number range [0, 65535], '.$var);
                }else{
                    if($var < -32768 || $var > 32767) throw new Exception($method.', Number range [-32768, 32767], '.$var);
                }
                break;
            }
            case 'mediumint':{
                if($unsigned === 1){
                    if($var < 0 || $var > 16777215) throw new Exception($method.', Number range [0, 16777215], '.$var);
                }else{
                    if($var < -8388608 || $var > 8388607) throw new Exception($method.', Number range [-8388608, 8388607], '.$var);
                }
                break;
            }
            case 'int':
            case 'timestamp':{
                if($unsigned === 1){
                    if($var < 0 || $var > 4294967295) throw new Exception($method.', Number range [0, 4294967295], '.$var);
                }else{
                    if($var < -2147483648 || $var > 2147483647) throw new Exception($method.', Number range [-2147483648, 2147483647], '.$var);
                }
                break;
            }
            case 'serial':
            case 'bigint': {
                if(is_int($var)){
                    if($unsigned === 1){
                        if($var < 0 || $var > PHP_INT_MAX) throw new Exception($method.', Number range [0, 9223372036854775807], '.$var);                                           // php max limit
//                        if($var < 0 || $var > 18446744073709551615) throw new Exception($method.', Number range [0, 18446744073709551615], '.$var);
                    }else{
                        if($var < PHP_INT_MIN || $var > PHP_INT_MAX) throw new Exception($method.', Number range [-9223372036854775808, 9223372036854775807], '.$var);
                    }
                }else if(is_string($var)){
                    if($unsigned === 1){
                        if(bccomp($var, '0') === -1 || bccomp($var, '9223372036854775807') === 1) throw new Exception($method.', Number range [0, 9223372036854775807], '.$var);    // php max limit
//                        if(bccomp($var, '0') === -1 || bccomp($var, '18446744073709551615') === 1) throw new Exception($method.', Number range [0, 18446744073709551615], '.$var);
                    }else{
                        if(bccomp($var, '-9223372036854775808') === -1 || bccomp($var, '9223372036854775807') === 1) throw new Exception($method.', Number range [-9223372036854775808, 9223372036854775807], '.$var);
                    }
                }else{
                    throw new Exception($method.', Number must type is int or string: '.gettype($var).', '.$var);
                }
                break;
            }
            case 'decimal':
            case 'float':
            case 'double':
            case 'real':{
                break;
            }
            default:{
                if(preg_match('/\((\d+)\)/', $columnTypeStr, $columnTypeMatch)){
                    $columnMaxLength = intval(trim($columnTypeMatch[1]??''));
                    if($columnMaxLength > 0 && CommString::strLenWord($var) > $columnMaxLength) throw new Exception($method.', String max length ['.$columnMaxLength.'], '.$var);
                }
            }
        }
    }

    /**
     * @param string $method
     * @param string $type
     * @param string $columnName
     * @param string $columnTypeStr
     * @param string $columnType
     * @param int $unsigned
     * @return bool
     * parseMethod
     */
    private static function parseMethod(string $method, string &$type, string &$columnName, string &$columnTypeStr, string &$columnType, int &$unsigned):bool
    {
        list($className, $methodName)   = explode('::', $method, 2);
        try{
            $ClassNameReflectionClass   = new ReflectionClass($className);
            $ReflectionMethod           = $ClassNameReflectionClass->getMethod($methodName);
            $methodDocComment           = $ReflectionMethod->getDocComment();
        }catch(ReflectionException $ReflectionException){
            return false;
        }
        if(is_string($methodDocComment) && preg_match('#\*(.*)\s+.*\s+.*<code>(\S+\s+)?(\S+)\s+\S+\s+=\s+(\d+);</code>#', $methodDocComment, $match)){
            $comment        = $match[1];
            $type           = str_replace('.', '\\', trim($match[3], '.'));
            $columnName     = '';
            $columnTypeStr  = '';
            $columnType     = '';
            $unsigned       = 0;
            if(preg_match('/'.(strpos($comment, '@') === false ? '&#64;' : '@').'(\[.*])/', $comment, $matchField)){
                $columnKeys     = CommJson::decodeArray($matchField[1]);
                $columnName     = $columnKeys[0]??'';
                $columnTypeStr  = $columnKeys[1]??'';
                if(strlen($columnTypeStr) > 0){
                    $unsigned   = strpos($columnTypeStr, 'unsigned') !== false ? 1 : 0;
                    if(preg_match('/(^[a-zA-Z]+)/', $columnTypeStr, $columnTypeMatch)) $columnType = strtolower(trim($columnTypeMatch[1]??''));
                }
            }
            return true;
        }
        return false;
    }
}